package model.mapping;

import java.util.LinkedList;
import java.util.List;

import model.item.Item;
import model.map.GameMap;
import model.map.GameTile;
import model.playable.PlayableUser;
import model.playable.RallyPoint;
import model.playable.structure.Structure;
import model.playable.unit.Unit;
import model.playable.unit.Vehicle;
import model.playable.unit.Worker;

public class PlayableMapping
{
	private class TilePlayableAssociation
	{
		private GameTile tile;
		private List<Unit> units;
		private Structure structure;
		private List<Worker> workers;
		private List<RallyPoint> rallys;
		private List<Vehicle> vehicles;
		
		public TilePlayableAssociation(GameTile tile)
		{
			this.tile = tile;
			units = new LinkedList<Unit>();
			structure = null;
			workers = new LinkedList<Worker>();
			rallys = new LinkedList<RallyPoint>();
			vehicles = new LinkedList<Vehicle>();
		}
		
		public GameTile getTile()
		{
			return tile;
		}
		
		public void addUnit(Unit u)
		{
			units.add(u);
		}
		
		public void putStructure(Structure s)
		{
			structure = s;
		}

		public void addWorker(Worker w)
		{
			workers.add(w);
		}

		public void addRallyPoint(RallyPoint r)
		{
			rallys.add(r);
		}
		
		public void addVehicle(Vehicle v)
		{
			vehicles.add(v);
		}

		public void removeUnit(Unit u)
		{
			units.remove(u);
		}
		
		public void removeStructure()
		{
			structure = null;
		}

		public void removeWorker(Worker w)
		{
			workers.remove(w);
		}

		public void removeRallyPoint(RallyPoint r)
		{
			rallys.remove(r);
		}
		
		public void removeVehicle(Vehicle v)
		{
			vehicles.remove(v);
		}

		public boolean containsUnit(Unit u)
		{
			return units.contains(u);
		}
		
		public boolean containsWorker(Worker w) 
		{
			return workers.contains(w);
		}

		public boolean containsRallyPoint(RallyPoint r)
		{
			return rallys.contains(r);
		}

		public boolean containsVehicle(Vehicle v)
		{
			return vehicles.contains(v);
		}
		
		public boolean hasStructure()
		{
			return structure != null;
		}
		
		public boolean hasUnits()
		{
			return !units.isEmpty();
		}
		
		public boolean hasWorkers()
		{
			return !workers.isEmpty();
		}
		
		public boolean hasRallyPoints()
		{
			return !rallys.isEmpty();
		}
		
		public boolean hasVehicles()
		{
			return !vehicles.isEmpty();
		}
		
		public Structure getStructure() 
		{
			return structure;
		}

		public List<Unit> getAllUnits()
		{
			return units;
		}

		public List<Worker> getAllWorkers()
		{
			return workers;
		}
		
		public List<RallyPoint> getAllRallyPoints() 
		{
			return rallys;
		}
		
		public List<Vehicle> getAllVehicles()
		{
			return vehicles;
		}

		public Vehicle getVehicle(int i)
		{
			return vehicles.get(i);
		}
		
		public boolean isPlayer(PlayableUser p)
		{
			return p == getOccupyingPlayer();
		}

		public PlayableUser getOccupyingPlayer()
		{
			if (hasStructure())
				return structure.getPlayer();
			if (hasUnits())
				return units.get(0).getPlayer();
			if (hasWorkers())
				return workers.get(0).getPlayer();
			if (hasRallyPoints())
				return rallys.get(0).getPlayer();
			if (hasVehicles())
				return rallys.get(0).getPlayer();
			
			return null;
		}

		public boolean isEmpty()
		{
			return !( hasUnits() || hasStructure() || hasWorkers() || hasRallyPoints() || hasVehicles());
		}
	}
	
	private static PlayableMapping playableMapping = new PlayableMapping();
	private ItemMapping itemMapping = ItemMapping.getItemMapping();
	
	private List<TilePlayableAssociation> playablesOnMap;
	private GameMap map;
	
	private PlayableMapping()
	{
		playablesOnMap = new LinkedList<TilePlayableAssociation>();
	}
	
	public void setMap(GameMap map)
	{
		this.map = map;
	}
	
	public void addUnit(GameTile tile, Unit u)
	{
		TilePlayableAssociation tpa;
		
		if (containsTile(tile))
		{
			System.out.println("tile is in mapping!");
			tpa = getTPA(tile);
			System.out.println(tpa == null);
		}
		else
		{
			System.out.println("No tile here");
			tpa = new TilePlayableAssociation(tile);
			
			playablesOnMap.add(tpa);
		}
		
		System.out.println("Adding unit to tile");
		System.out.println(tpa.getTile() == tile);
		
		tpa.addUnit(u);
		
		for (Unit un : tpa.getAllUnits())
			System.out.println(un);
		
		triggerItemOnTile(tile);
	}
	
	public void addStructure(GameTile tile, Structure s)
	{
		TilePlayableAssociation tpa;
		
		if (containsTile(tile))
			tpa = getTPA(tile);
		else
		{
			tpa = new TilePlayableAssociation(tile);
			
			playablesOnMap.add(tpa);
		}
		
		triggerItemOnTile(tile);
		
		tpa.putStructure(s);
	}

	public void addWorker(GameTile tile, Worker w)
	{
		TilePlayableAssociation tpa;
		
		if (containsTile(tile))
			tpa = getTPA(tile);
		else
		{
			tpa = new TilePlayableAssociation(tile);
			
			playablesOnMap.add(tpa);
		}
		
		triggerItemOnTile(tile);
		
		tpa.addWorker(w);
	}

	public void addRallyPoint(GameTile tile, RallyPoint r)
	{
		TilePlayableAssociation tpa;
		
		if (containsTile(tile))
			tpa = getTPA(tile);
		else
		{
			tpa = new TilePlayableAssociation(tile);
			
			playablesOnMap.add(tpa);
		}
		
		triggerItemOnTile(tile);
		
		tpa.addRallyPoint(r);
	}

	public void addVehicle(GameTile tile, Vehicle v)
	{
		TilePlayableAssociation tpa;
		
		if (containsTile(tile))
			tpa = getTPA(tile);
		else
		{
			tpa = new TilePlayableAssociation(tile);
			
			playablesOnMap.add(tpa);
		}
		
		triggerItemOnTile(tile);
		
		tpa.addVehicle(v);
	}
	
	private void triggerItemOnTile(GameTile tile) 
	{
		itemMapping.triggerItemOn(tile);
	}

	public void removeUnit(Unit u)
	{
		TilePlayableAssociation tpa = getTPA(getLocationOfUnit(u));
		if (tpa != null)
			tpa.removeUnit(u);
		else
			throw new RuntimeException("Trying to remove unit that's not on the map!");
	}
	
	public void removeStructure(Structure s)
	{
		TilePlayableAssociation tpa = getTPA(getLocationOfStructure(s));
		if (tpa != null)
			tpa.removeStructure();
		else
			throw new RuntimeException("Trying to remove structure that's not on the map!");
	}
	
	public void removeWorker(Worker w)
	{
		TilePlayableAssociation tpa = getTPA(getLocationOfWorker(w));
		if (tpa != null)
			tpa.removeWorker(w);
		else
			throw new RuntimeException("Trying to remove worker that's not on the map!");
	}
	
	public void removeRallyPoint(RallyPoint r)
	{
		TilePlayableAssociation tpa = getTPA(getLocationOfRallyPoint(r));
		if (tpa != null)
			tpa.removeRallyPoint(r);
		else
			throw new RuntimeException("Trying to remove rally point that's not on the map!");
	}

	public void removeVehicle(Vehicle v)
	{
		TilePlayableAssociation tpa = getTPA(getLocationOfVehicle(v));
		if (tpa != null)
			tpa.removeVehicle(v);
		else
			throw new RuntimeException("Trying to remove unit that's not on the map!");
	}
	
	private boolean containsTile(GameTile tile)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if (tpa.getTile() == tile)
				return true;
		}
		return false;
	}

	private TilePlayableAssociation getTPA(GameTile tile)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if (tpa.getTile() == tile)
				return tpa;
		}
		TilePlayableAssociation tpa = new TilePlayableAssociation(tile);
		playablesOnMap.add(tpa);
		return tpa;
	}
	
	public GameTile getLocationOfUnit(Unit u)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if (tpa.containsUnit(u))
				return tpa.getTile();
		}
		return null;
	}
	
	public GameTile getLocationOfStructure(Structure s)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if ( s == tpa.getStructure())
				return tpa.getTile();
		}
		return null;
	}
	
	public GameTile getLocationOfWorker(Worker w)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if (tpa.containsWorker(w))
				return tpa.getTile();
		}
		return null;
	}
	
	public GameTile getLocationOfRallyPoint(RallyPoint r)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if (tpa.containsRallyPoint(r))
				return tpa.getTile();
		}
		return null;
	}
	
	public GameTile getLocationOfVehicle(Vehicle v)
	{
		for (TilePlayableAssociation tpa : playablesOnMap)
		{
			if (tpa.containsVehicle(v))
				return tpa.getTile();
		}
		return null;
	}

	public List<Unit> getUnitsOn(GameTile tile)
	{
		List<Unit> playables = new LinkedList<Unit>();
		
		if (containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			if (tpa.hasUnits())
				playables.addAll(tpa.getAllUnits());
		}
		return playables;
	}
	
	public Structure getStructureOn(GameTile tile)
	{
		
		if(containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			return tpa.getStructure();
		}
		return null;
	}
	
	public List<Worker> getWorkersOn(GameTile tile)
	{
		List<Worker> playables = new LinkedList<Worker>();
		
		if (containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			if (tpa.hasWorkers())
				playables.addAll(tpa.getAllWorkers());
		}
		return playables;
	}
	
	public List<RallyPoint> getRallyPointsOn(GameTile tile)
	{
		List<RallyPoint> playables = new LinkedList<RallyPoint>();
		
		if (containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			if (tpa.hasRallyPoints())
				playables.addAll(tpa.getAllRallyPoints());
		}
		return playables;
	}

	public List<Vehicle> getVehiclesOn(GameTile tile)
	{
		List<Vehicle> playables = new LinkedList<Vehicle>();
		
		if (containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			if (tpa.hasVehicles())
				playables.addAll(tpa.getAllVehicles());
		}
		return playables;
	}
	
	public List<GameTile> getTilesAroundUnit(Unit u, int radius)
	{
		GameTile tile = getLocationOfUnit(u);
		
		return map.getTilesAround(tile, radius);
	}
	
	public List<GameTile> getTilesAroundStructure(Structure s, int radius)
	{
		GameTile tile = getLocationOfStructure(s);
		
		return map.getTilesAround(tile, radius);
	}
	
	public List<GameTile> getTilesAroundWorker(Worker w, int radius)
	{
		GameTile tile = getLocationOfWorker(w);
		
		return map.getTilesAround(tile, radius);
	}
	
	public List<GameTile> getTilesAroundVehicle(Vehicle v, int radius)
	{
		GameTile tile = getLocationOfVehicle(v);
		
		return map.getTilesAround(tile, radius);
	}
	
	public Vehicle getFirstAvailableVehicleOn(GameTile tile)
	{
		if (containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			if (tpa.hasVehicles())
				return tpa.getVehicle(0);
		}
		return null;
	}
	
	public boolean isObstructedForPlayer(GameTile tile, PlayableUser player)
	{
		if (containsTile(tile))
		{
			TilePlayableAssociation tpa = getTPA(tile);
			
			return !(tpa.isEmpty() || tpa.isPlayer(player) || !itemMapping.isBlocked(tile));
		}
		return itemMapping.isBlocked(tile);
	}
	
	public void affectWithItem(GameTile tile, Item item)
	{
		List<Unit> units = getUnitsOn(tile);
		
		for (Unit u : units)
			item.affect(u);
		
		if (getTPA(tile).hasStructure())
			item.affect(getStructureOn(tile));
		
	}
	
	public void update()
	{
		List<TilePlayableAssociation> list = new LinkedList<TilePlayableAssociation>(playablesOnMap);
		
		for (TilePlayableAssociation tpa : list)
		{
			GameTile tile = tpa.getTile();
			if (tile.hasCurrent() && tile.hasNeighborAt(tile.getCurrentDirection()))
			{
				List<Unit> units = new LinkedList<Unit>(tpa.getAllUnits());
				
				for (Unit u : units)
				{
					u.move(tile.getCurrentDirection());
				}
			}
		}
	}

	public static PlayableMapping getPlayableMapping()
	{
		return playableMapping;
	}
}
